<?php

namespace App\Models;


use App\Service\BangCache;
use App\Service\Option;
use Illuminate\Database\Eloquent\Model as BaseModel;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;


// 测试
class Base202505 extends BaseModel
{
    const CREATED_AT = 'ctime';
    const UPDATED_AT = 'mtime';

    protected $dateFormat = 'U';
    protected $guarded    = [];
    protected $query;

    // 模型读取的时间戳
    protected $casts = [
        //'created_at' => 'date:Y-m-d',           // 日期
        //'deleted_at' => 'datetime:Y-m-d h:i:s'    // 日期时间
        'ctime' => 'timestamp',              // 时间戳
        'mtime' => 'timestamp',              // 日期
    ];

    //
    protected $error;

    // 缓存
    protected $cacheFields = ['id']; // 缓存字段，用于搜索绑定的字段
    protected $is_cache;
    protected $bangData;

    // 初始化
    public function __construct(array $data = [])
    {
        parent::__construct($data);

        // 开启sql日志
        DB::connection()->enableQueryLog();
        // 获取真是表名（在执行数据库操作的时候在来获取）
        // $this->initialize();
    }

    // 初始化(主要用于组件多表查询)
    protected function initialize() { }


    // 获取图片
    public function getPicAttribute($value)
    {
        return $this->replaceDomain($value, 'full');
    }

    // 保存图片
    public function setPicAttribute($value)
    {
        return $this->replaceDomain($value, 'old');
    }




    ##########################################################################################
    ## 数据库操作
    // 获取多条数据 不翻页
    public function getList($where = [], $number = 10, $order = "id DESC", $offset = 0)
    {
        // 获取缓存
        $lists   = null;
        $isCache = $this->is_cache;
        $args    = compact('where', 'number', 'order', 'offset');
        if($isCache){
            $bang  = new BangCache();
            $cName = $bang->makeCacheKey($this->table, 'list', $args);   // 所有参数执行 key
            $lists = $bang->getCache($cName);
        }

        // 查询
        if(is_null($lists)){
            $fields = $this->getTableField(true);
            $query  = $this->orderByRaw($order);
            if($number){
                $lists = $query->newQuery()->where($where)->offset($offset)->select($fields)->limit($number)->get()->toArray();
            } else{
                $lists = $query->newQuery()->where($where)->select($fields)->get()->toArray();
            }

            // 用于绑定缓存
            if($isCache){
                // 保存缓存
                $bang->addCache($cName, $lists);
                // 保存为绑定
                $bangData = empty($this->bangData) ? [] : $this->bangData;
                $this->addBang($bangData, $cName, 'list');
            }
        }

        $this->clearCache();

        return $lists;
    }

    // 获取多条数据（参数只有ids，因为使用in没办法缓存，所以这么操作）
    // 获取多条数据（参数只有ids，如果查询某个地方指定会查询指定id的记录，因为id不同，缓存不容易，如果用oneFind则可能查询字段较多，所以创建这个方法）
    public function getListInIds($ids, $fields = ['*'], $pk = 'id')
    {
        // 如果不要缓存 直接查询
        $isCache = $this->is_cache;
        if(empty($isCache)){
            $lists = $this->select($fields)->whereIn($pk, $ids)->get()->toArray();
            $lists = array_column($lists, null, $pk);

            return $lists;
        }

        // 如果缓存 则查询缓存
        $lists = [];
        foreach($ids as $id){
            // 获取缓存
            /*$args      = compact('id', 'fields', 'pk');
            $cacheName = $this->cacheKey('getListInIds', $args);
            $cacheData = $this->cacheClass()->getCache($cacheName);
            if(empty($cacheData)){
                // 查询
                $info = $this->where($pk, $id)->select($fields)->first();
                $info = is_object($info) ? $info->toArray() : $info;
                $this->addCacheShow($info, $cacheName, $info);
                $cacheData = $info;
            }
            if($cacheData){
                $lists[$id] = $cacheData;
            }*/

            // 获取数据
            $info = $this->isCache(true)->getOneById($id, $fields, $pk);
            if($info){
                $lists[$id] = $info;
            }
        }

        $this->clearCache();

        return $lists;
    }


    // 获取多条数据（参数只有ids，因为使用in没办法缓存，所以这么操作）
    // 获取多条数据（参数只有ids，如果查询某个地方指定会查询指定id的记录，因为id不同，缓存不容易，如果用oneFind则可能查询字段较多，所以创建这个方法）
    public function getOneById($id, $fields = ['*'], $pk = 'id')
    {
        if(empty($id)){
            return [];
        }

        // 如果不要缓存 直接查询
        $getOneById = true;
        $isCache    = $this->is_cache;
        if(empty($isCache)){
            $info = $this->select($fields)->where($pk, $id)->first();
            $info = is_object($info) ? $info->toArray() : $info;

            return $info;
        }

        // 如果缓存 则查询缓存
        $args  = compact('id', 'fields', 'pk', 'getOneById');
        $bang  = new BangCache();
        $cName = $bang->makeCacheKey($this->table, 'show', $args);   // 所有参数执行 key
        $info  = $bang->getCache($cName);

        // 查询
        if(empty($info)){
            $info = $this->newQuery()->where($pk, $id)->select($fields)->first();
            $info = is_object($info) ? $info->toArray() : $info;

            // 保存缓存
            $bang->addCache($cName, $info);
            // 添加绑定
            //$bangData = empty($this->bangData) ? ['id' => $info['id']] : $this->bangData;
            $bangData = $this->bangData;
            if(empty($bangData)){
                $bangData = $info ? ['id' => $info['id']] : [];
            }

            $tags = $bang->makeBang($this->table, $bangData, 'show');
            foreach($tags as $_tag){
                $bang->addTag($_tag, $cName);
            }
        }

        $this->clearCache();

        return $info;
    }

    // 获取多条数据+翻页
    public function listPage($where = [], $page = 1, $rows = 10, $order = "id DESC")
    {
        // 获取缓存
        $isCache = $this->is_cache;
        $args    = compact('where', 'page', 'rows', 'order');
        if($isCache){
            $bang   = new BangCache();
            $cName  = $bang->makeCacheKey($this->table, 'lists', $args);   // 所有参数执行 key
            $result = $bang->getCache($cName);
        }

        // 执行查询
        if(empty($result)){
            // 字段
            $fields = $this->getTableField(true);
            $query  = $this->newQuery()->select($fields)->forPage($page, $rows)->orderByRaw($order);

            // 查询  ->recycle()  ->orderby($order)
            if(empty($where)){
                $count = $this->count();
            } else{
                if(is_array($where)){
                    $query = $query->where($where);
                    $count = $this->where($where)->count();
                } elseif(is_string($where)){
                    $query = $query->whereRaw($where);
                    $count = $this->whereRaw($where)->count();
                }
            }

            // 查找过滤
            $is_next = 0;
            $lists   = $query->get()->toArray();
            // 查看是否需要翻页
            if($page * $rows < $count){
                $is_next = 1;
            }

            // 保存为缓存
            $result = ['total' => $count, 'page' => $page, 'rows' => $rows, 'is_next' => $is_next, 'list' => $lists,];
            if($isCache){
                // 保存缓存
                $bang->addCache($cName, $result);
                // 添加绑定
                $bangData = empty($this->bangData) ? [] : $this->bangData;
                $this->addBang($bangData, $cName, 'list');
            }
        }

        $this->clearCache();

        return $result;
    }

    // 查询一个
    public function oneFind($where, $order = "id DESC")
    {
        // 获取缓存
        $isCache = $this->is_cache;
        $args    = compact('where', 'order');
        if($isCache){
            $bang  = new BangCache();
            $cName = $bang->makeCacheKey($this->table, 'show', $args);   // 所有参数执行 key
            $info  = $bang->getCache($cName);
            if(!empty($info)){
                return $info;
            }
        }

        // 执行查询
        $info = $this->newQuery()->where($where)->orderByRaw($order)->first(); // ->recycle()
        if(empty($info)){
            return $this->return_false('数据不存在！！');
        }

        // 保存为缓存
        if($isCache){
            // 保存缓存
            $bang->addCache($cName, $info);
            // 添加绑定
            $bangData = empty($this->bangData) ? ['id' => $info['id']] : $this->bangData;
            $this->addBang($bangData, $cName, 'show');
        }

        $this->clearCache();

        return $info;
    }


    // 添加
    public function oneAdd($data = [])
    {
        $this->error = '';

        // 模型 和 查询
        //$data = $this->filterNull($data);
        $this->fill($data);
        $result = $this->save();

        //$result = $model->create($data);  // create方法需要制定$fillable 所以这和上面操作是一样的
        // 或者
        //$data   = $model->fieldArray($data);
        //$result = $model->save($data);

        // 缓存操作
        $this->deleteCacheShow($data);
        $this->clearCache();

        return $this->id;
    }

    // 修改
    public function oneEdit($where, $data)
    {
        $info = $this->newQuery()->where($where)->first();  // ->recycle()
        if(empty($info)){
            return $this->return_false('你要修改的数据不存在！！');
        }

        // 保存
        //$data = $this->filterNull($data);
        //$info->fill($data);
        //$result = $info->save();

        // 保存
        $result = $this->newQuery()->where($where)->update($data);

        // 或者
        //$data   = $model->fieldArray($data);
        //$result = $model->where($where)->update($data);

        // 缓存操作
        $this->deleteCacheShow($info, $data);
        $this->clearCache();

        return $result;
    }

    // 删除
    public function oneDelete($where)
    {
        // ->newQuery()  // 删除以前查询的where
        $info = $this->newQuery()->where($where)->first();  //->recycle()
        if(empty($info)){
            return $this->return_false('你要删除的数据不存在！！');
        }

        // 缓存操作
        $this->deleteCacheShow($info);
        $this->clearCache();

        return $info->delete();
    }

    // 执行软删除
    public function oneSoftDelete($where, $field = 'verify')
    {
        $info = $this->newQuery()->where($where)->first();  //->recycle()
        if(empty($info)){
            return $this->return_false('你要删除的数据不存在！！');
        }

        $this->where('id', $info['id'])->update([$field => -1]);

        // 缓存操作
        $this->deleteCacheShow($info);
        $this->clearCache();

        return true;
    }

    // 删除
    public function allDelete($ids)
    {
        $list = $this->whereIn('id', $ids)->first();  //->recycle()
        if(empty($list)){
            return $this->return_false('你要删除的数据不存在！！');
        }

        // 缓存操作
        $this->deleteCacheList($list);
        $this->clearCache();

        return $list->delete();
    }

    // join操作（暂未完成）
    public function joinList($where, $page, $field, $rows, $orderBy)
    {
        $query = $this->newQuery()->from('article model')->where($where)
                      ->join('article_topic as topic', 'topic.article_id', '=', 'article.id')
                      ->join('article_cate as cate', 'cate.article_id', '=', 'article.id');

        if($page){
            // 除了作家以外，都要排除最新三条数据（作家没有顶部置顶三条数据）
            $count    = $query->count();
            $articles = $query->select($field)->orderByRaw($orderBy)->forPage($page, $rows)->get()->toArray();
        } else{
            $articles = $query->orderBy('article.id', 'desc')->get()->toArray();
        }

        // 返回
        return $count;
    }

    // 分组返回数据（暂未完成）
    public function onGroupList($number = 1, $orderBy = "a.ctime DESC")
    {
        $table  = DB::getTablePrefix().'a';
        $count  = $number + 1;
        $_table = $this->getTable();
        // select count(*) from my8_bracelet_information where uid=a.uid and created_at < a.created_at having Count(*) < 2
        $subSql = $this->selectRaw('count(*)')->whereIn('author_id', $authorIds)->whereIn('a.author_id', $authorIds)
                       ->whereRaw('author_id=la_a.author_id and ctime < la_a.ctime')->havingRaw("count(*) < $count");
        // select a.* from my8_bracelet_information a where exists ($subSql)  order by a.created_at DESC;
        // ->from($articleDb->getTable().' as a')
        $articles = $this->from($_table." as a")->select('a.*')->mergeBindings($subSql->getQuery())
                         ->whereIn('a.author_id', $authorIds)
                         ->whereRaw("exists (".$subSql->toSql().")")->orderByRaw($orderBy)->get()->toArray();

        return $articles;
    }

    ##########################################################################################
    ## 字段

    // 获取数据表所有的字段
    public function getTableField($simple = false)
    {
        $table    = $this->getTable();
        $tableKey = $table.'_fields';
        $columns  = Cache::store('field')->get($tableKey);
        // 如果没有字段信息 重新在数据库中查询
        if(empty($columns)){
            //DB::enableQueryLog();

            //$fields  = Schema::getColumnListing($table); // 只有字段名称
            $dbName    = DB::getDatabaseName();
            $fulltable = DB::getTablePrefix().$table;
            $field_str = "COLUMN_NAME field,DATA_TYPE `itype`, CHARACTER_MAXIMUM_LENGTH  `length`, ";
            $field_str .= " IS_NULLABLE is_null, COLUMN_DEFAULT `default`, COLUMN_COMMENT comment";
            $where_sql = " WHERE table_name= ? AND  TABLE_SCHEMA=? ";
            $sql       = 'SELECT '.$field_str.' FROM information_schema.columns '.$where_sql;
            $result    = DB::select($sql, [$fulltable, $dbName]);
            $columns   = json_decode(json_encode($result), true);
            $columns   = array_column($columns, null, 'column_name');

            unset($columns['is_recycle']);  // 删除字段 is_recycle
            Cache::store('field')->set($tableKey, $columns);

            // 数据库语句
            //$sqls = DB::getQueryLog();
            //var_dump($sqls);
        }

        // 值需要字段就好
        if($simple){
            $columns = array_column($columns, 'field');
        }

        return $columns;
    }


    //获取全表名
    public function fullTable()
    {
        $table = $this->getTable();
        $table = DB::getTablePrefix().$table;

        return $table;
    }

    // 制作表单验证
    public function fieldRule()
    {
        // 判断是否有缓存
        $table    = $this->getTable();
        $tableKey = $table.'_rules';
        $rules    = Cache::store('fieldRule')->get($tableKey);
        if(!empty($rules)){
            return $rules;
        }

        $rules  = [];
        $fields = $this->getTableField();
        foreach($fields as $_item){
            // 参数
            $_field   = $_item['field'];
            $_itype   = $_item['itype'];
            $_length  = $_item['length'];
            $_default = $_item['default'];
            $_rule    = [];

            // 判断
            if($_field == 'id'){
                continue;
            }

            // 制作验证规则
            if(strpos($_itype, 'int') !== false){
                $_rule[] = 'integer';
            }

            if($_itype == 'tinyint'){
                $_rule[] = 'max:32';
            } elseif($_itype == 'varchar' && $_length){
                $_rule[] = 'max:'.$_length;
            } elseif($_itype == 'text'){
                $_rule[] = 'max:'.$_length;
            }

            // 整合验证规则（如果不存在，则该字段传值则不能为空）
            // $_default
            if(!empty($_rule) && ($_field != 'ip_address')){
                array_unshift($_rule, 'sometimes', 'required');
                $rules[$_field] = $_rule;
            }
        }

        // 缓存
        Cache::store('fieldRule')->set($tableKey, $rules);

        return $rules;
    }

    // 获取自增长值
    public function getAutoInc()
    {
        $table     = $this->getTable();
        $dbName    = DB::getDatabaseName();
        $fulltable = DB::getTablePrefix().$table;
        $doSql     = "SELECT auto_increment FROM information_schema.tables  WHERE TABLE_NAME= ? AND TABLE_SCHEMA = ? ";
        $result    = DB::select($doSql, [$fulltable, $dbName]);

        return $result[0]->auto_increment;
    }

    // 获取需要查询的字段

    ##########################################################################################
    ## 缓存操作

    // 缓存
    public function isCache($is_cache = true)
    {
        $this->is_cache = $is_cache;

        return $this;
    }

    // 缓存用于绑定的缓存（如果没有缓存的时候，一般不能保存，所以这里用于绑定）
    public function bangData($data)
    {
        if(empty($data)){
            return true;
        }

        // 重新绑定
        $this->bangData = empty($this->bangData) ? $data : array_merge($this->bangData, $data);
        $this->is_cache = true;

        // 判断绑定的字段不存在与模型设置的表缓存字段中的
        // 绑定的字段
        $fields = array_keys($data);
        $res    = $this->checkBangField($fields);
        if($res === false){
            return $res;
        }

        return true;
    }

    // 添加绑定
    public function addBang($bangData, $cFile, $self = 'list')
    {
        $bang = new BangCache();
        $tags = $bang->makeBang($this->table, $bangData, $self);
        foreach($tags as $_tag){
            $bang->addTag($_tag, $cFile);
        }
    }

    // 删除详情缓存
    // 删除详情的时候，删除缓存字段值的绑定（所以操作删除缓存的时候，要先查询出详情）
    public function deleteCacheShow($info, $old = [])
    {
        $bang   = new BangCache();
        $table  = $this->table;
        $fields = $this->getCacheFields();
        // 删除绑定缓存
        if($this->bangData){
            $tags = $bang->makeBang($table, $this->bangData);
            $bang->deleteTag($tags);
        }

        // 删除绑定缓存
        $map = [];
        foreach($fields as $_field){
            if(isset($info[$_field])){
                $map[$_field] = $info[$_field];
            }
        }
        $tags = $bang->makeBang($table, $map);
        $bang->deleteTag($tags);


        // 删除绑定缓存
        if($old){
            $map = [];
            foreach($fields as $_field){
                if(isset($old[$_field])){
                    $map[$_field] = $old[$_field];
                }
            }
            if($map){
                $tags = $bang->makeBang($table, $map);
                $bang->deleteTag($tags);
            }
        }


        // 删除其他绑定缓存
        $tags = [$table, $table.'_list', $table.'_lists', $table.'_count', $table.'_join', $table.'_tree',];
        $bang->deleteTag($tags);

    }

    // 删除列表缓存
    // TODO 几乎很少用到，只有在批量操作时候才有用 或者 某个条件（如某个外键）的删除
    public function deleteCacheList($list)
    {
        $bang   = new BangCache();
        $table  = $this->table;
        $fields = $this->getCacheFields();
        // 删除绑定缓存
        if($this->bangData){
            $tags = $bang->makeBang($table, $this->bangData);
            $bang->deleteTag($tags);
        }

        // 删除绑定缓存
        foreach($list as $row){
            $map = [];
            foreach($fields as $_field){
                if(isset($row[$_field])){
                    $map[$_field] = $row[$_field];
                }
            }
            // 删除
            $tags = $bang->makeBang($table, $map);
            $bang->deleteTag($tags);
        }

        // 删除其他绑定缓存
        $tags = [$table, $table.'_list', $table.'_lists', $table.'_count', $table.'_join', $table.'_tree',];
        $bang->deleteTag($tags);

    }

    // 缓存字段
    protected function getCacheFields()
    {
        $fields = $this->cacheFields;
        if(empty($fields)){
            return ['id'];
        }

        if(is_string($fields)){
            $fields = explode(',', $fields);
        }
        if(!in_array('id', $fields)){
            $fields[] = 'id';
        }

        return $fields;
    }


    // 使用反射 获取当前类方法的缓存文件
    public function getFunctionCacheData($function, $args = [])
    {
        $cache  = $this->cacheKey($function, $args);
        $result = $this->cacheClass()->getCache($cache);

        return $result;
    }

    ##########################################################################################
    ## 使用反射当前模型

    // 使用反射 获取当前类方法的参数
    public function getReflectionParam($function, $all_params = [])
    {
        $args    = [];
        $reflect = new \ReflectionClass(self::class);
        $method  = $reflect->getMethod($function);
        $params  = $method->getParameters();
        foreach($params as $key => $_param){
            $k_name = $_param->getName();
            $v_name = $all_params[$key] ?? $_param->getDefaultValue();
            // 整理
            $args[$k_name] = $v_name;
        }

        return $args;
    }

    // 使用反射 获取当前类方法的缓存文件
    public function getReflectionCacheFile($function, $all_params = [])
    {
        $args    = [];
        $reflect = new \ReflectionClass(self::class);
        $method  = $reflect->getMethod($function);
        $params  = $method->getParameters();
        foreach($params as $key => $_param){
            $k_name = $_param->getName();
            $v_name = $all_params[$key] ?? $_param->getDefaultValue();
            // 整理
            $args[$k_name] = $v_name;
        }

        $cFile = $this->cacheFile($function, $args);

        return $cFile;
    }

    // 利用反射获取缓存
    public function getReflectionCache($function, $all_params = [])
    {
        $info = null;
        $has  = $this->is_cache;
        if($has){
            $args    = [];
            $reflect = new \ReflectionClass(self::class);
            $method  = $reflect->getMethod($function);
            $params  = $method->getParameters();
            foreach($params as $key => $_param){
                $k_name = $_param->getName();
                $v_name = $all_params[$key] ?? $_param->getDefaultValue();
                // 整理
                $args[$k_name] = $v_name;
            }

            $cFile = $this->cacheFile($function, $args);
            $info  = $this->getCache($cFile);
        }

        return $info;
    }


    ##########################################################################################

    // 返回错误信息
    protected function return_false($msg = '操作失败！！')
    {
        $this->error = $msg;

        return false;
    }

    // 模型文件里面设置的验证规则（由每个模型文件改写）
    ## 特殊验证规则 +（最小长度，最小值）
    // 枚举：
    // 必填：
    // 邮箱等：
    protected function rules() { return []; }

    // 替换域名（保存到数据库中和获取到页面显示改变数据中的域名地址）（用于修改器与获取器）
    protected function replaceDomain($value, $to = 'full')
    {
        if(empty($value)){
            return $value;
        }

        // 替换
        $option    = new Option();
        $oldDomain = $option->domain('old');
        $nowDomain = $option->domain('oss');

        // full：展示
        if($to == 'full'){
            $value = str_replace($oldDomain, $nowDomain, $value);
        } else{
            // old：保存 保存为最原始的域名
            $value = str_replace($nowDomain, $oldDomain, $value);
        }

        return $value;
    }

    // 获取错误信息
    public function getError()
    {
        return $this->error;
    }

    // 过滤不存在表中的字段
    public function filterField($data, $delNull = false)
    {
        $fields = $this->getTableField(true);
        foreach($data as $key => $val){
            if(!in_array($key, $fields)){
                unset($data[$key]);
                continue;
            }
            if($delNull && is_null($data[$key])){
                unset($data[$key]);
            }
        }

        return $data;
    }

    // 获取执行的sql
    public function getSql()
    {
        return DB::getQueryLog();
    }

    // 获取执行的最后一条sql
    public function getLastSql()
    {
        $sqls = DB::getQueryLog();

        return $sqls[count($sqls) - 1] ?? '';
    }

    // 释放sql内存（尤其是命令行模式下，语句都存在内存中，容易溢出）
    public function flushQueryLog()
    {
        DB::flushQueryLog();
    }

    // 检查绑定的字段是否设置了缓存字段
    public function checkBangField($fields)
    {
        // 判断缓存字段是否存在
        $cFields = $this->cacheFields;
        if(empty($cFields)){
            return $this->return_false('请设置缓存字段！');
        }
        if(is_string($cFields)){
            $cFields = explode(',', $cFields);
        }
        // 不存在的字段
        $noFields = '';
        $comm     = '';
        foreach($fields as $_field){
            if(!in_array($_field, $cFields)){
                $noFields .= $comm.$_field;
                $comm     = '，';
            }
        }
        if($noFields){
            return $this->return_false('字段：'.$noFields.' 未设置缓存字段！');
        }

        return true;
    }

    // 清空绑定缓存
    public function clearCache()
    {
        $this->bangData = null;
        $this->is_cache = null;
    }
}


